#include <malloc.h>
#include "2dUtil.h"
#include "gfxUtils.h"

static void *frameBuffer[2] = { NULL, NULL};
GXRModeObj *rmode;
u32	fb = 0; 	// initial framebuffer index

#define DEFAULT_FIFO_SIZE	(1024*1024)
int tilesCols, tilesRows;

GXRModeObj* VideoInit()
{
	VIDEO_Init();
	rmode = VIDEO_GetPreferredMode(NULL);
	VIDEO_Configure(rmode);

	// allocate 2 framebuffers for double buffering
	frameBuffer[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
	frameBuffer[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
	VIDEO_ClearFrameBuffer(rmode, frameBuffer[fb], 0);
	VIDEO_SetNextFramebuffer(frameBuffer[fb]);
	VIDEO_SetBlack(FALSE);
	VIDEO_Flush();
	VIDEO_WaitVSync();
	if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
	return rmode;
}

void VideoCleanup()
{
	VIDEO_SetBlack(TRUE);
	VIDEO_Flush();
	VIDEO_WaitVSync();
	free(MEM_K1_TO_K0(frameBuffer[0]));
	free(MEM_K1_TO_K0(frameBuffer[1]));
	frameBuffer[0] = NULL;
	frameBuffer[1] = NULL;
}

void* getCurrentFrameBuffer()
{
	return frameBuffer[fb];
}

void* getSpareFrameBuffer()
{
	return frameBuffer[fb?0:1];
}

void blankScreen()
{
	VIDEO_SetBlack(FALSE);
	VIDEO_Flush();
}

void swapFrameBuffers()
{
	fb ^= 1;		// flip framebuffer
	VIDEO_SetNextFramebuffer(frameBuffer[fb]);
	VIDEO_Flush();
}

void waitVSync()
{
	VIDEO_WaitVSync();
}

void GraphicsInit()
{
	f32 yscale;
	u32 xfbHeight;
	Mtx perspective, GXmodelView2D;

	// setup the fifo and then init the flipper
	void *gp_fifo = NULL;
	gp_fifo = memalign(32,DEFAULT_FIFO_SIZE);
	memset(gp_fifo,0,DEFAULT_FIFO_SIZE);
 
	GX_Init(gp_fifo,DEFAULT_FIFO_SIZE);
 
	// clears the bg to color and clears the z buffer
	GXColor background = {0, 0, 0, 0xff};
	GX_SetCopyClear(background, 0x00ffffff);
 
	// other gx setup
	yscale = GX_GetYScaleFactor(rmode->efbHeight,rmode->xfbHeight);
	xfbHeight = GX_SetDispCopyYScale(yscale);
	GX_SetScissor(0,0,rmode->fbWidth,rmode->efbHeight);
	GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight);
	GX_SetDispCopyDst(rmode->fbWidth,xfbHeight);
	GX_SetCopyFilter(rmode->aa,rmode->sample_pattern,GX_TRUE,rmode->vfilter);
	GX_SetFieldMode(rmode->field_rendering,((rmode->viHeight==2*rmode->xfbHeight)?GX_ENABLE:GX_DISABLE));
	if (rmode->aa)
		GX_SetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR);
	else
		GX_SetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
	GX_SetDispCopyGamma(GX_GM_1_0);

	// setup the vertex descriptor
	// tells the flipper to expect direct data
	GX_ClearVtxDesc();
	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
	GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);

	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_F32, 0);
	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
	
	GX_SetNumChans(1);
	GX_SetNumTexGens(1);
	GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE);
	GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
	GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);

	guMtxIdentity(GXmodelView2D);
	guMtxTransApply (GXmodelView2D, GXmodelView2D, 0.0F, 0.0F, -5.0F);
	GX_LoadPosMtxImm(GXmodelView2D,GX_PNMTX0);

	guOrtho(perspective,0,479,0,639,0,300);
	GX_LoadProjectionMtx(perspective, GX_ORTHOGRAPHIC);

	GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
	GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
	GX_SetAlphaUpdate(GX_TRUE);
	
	GX_SetCullMode(GX_CULL_NONE);

	GX_InvalidateTexAll();
}

void loadPngTiles(const u8* pngBuffer, int numCols, int numRows)
{
	const png_byte* testPtr = reinterpret_cast<png_byte const*>(pngBuffer);
	png_infop info = read_png_file( testPtr );
	u8* tilesTex = reinterpret_cast<u8*>(memalign(32,info->width*info->height*4));
	png2tex(tilesTex);
	DCFlushRange(tilesTex, info->width*info->height*4);

	GXTexObj texObj;
	GX_InitTexObj(&texObj, MEM_K0_TO_K1((void*)tilesTex), info->width, info->height, GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE);
	GX_LoadTexObj(&texObj, GX_TEXMAP0);
	tilesCols = numCols;
	tilesRows = numRows;
}

//---------------------------------------------------------------------------------
void drawTile( int x, int y, int width, int height, int image, f32 angle )
{
//---------------------------------------------------------------------------------

	// Set up transform
	Mtx23 transform;
	guMtx23RotDeg(transform, angle);
	guMtx23ScaleApply(transform, transform, width, height);
	guMtx23TransApply(transform, transform, x-width/2.0f, y-width/2.0f);
	
	// set up unity quad
	Mtx24 quad;
	guMtx24Concat(transform, unitQuad, quad);

	int tileX = image % tilesCols;
	int tileY = image / tilesCols;
	float s1 = tileX/(float)tilesCols;
	float s2 = (tileX+1)/(float)tilesCols;
	float t1 = tileY / (float)tilesRows;
	float t2 = (tileY+1)/(float)tilesRows;

	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);			// Draw A Quad
		GX_Position2f32(quad[0][0], quad[1][0]);	// Top Left
		GX_TexCoord2f32(s1,t1);

		GX_Position2f32(quad[0][1], quad[1][1]);	// Top Right
		GX_TexCoord2f32(s2,t1);

		GX_Position2f32(quad[0][2], quad[1][2]);	// Bottom Right
		GX_TexCoord2f32(s2,t2);

		GX_Position2f32(quad[0][3], quad[1][3]);	// Bottom Left
		GX_TexCoord2f32(s1,t2);
	GX_End();						// Done Drawing The Quad 

}

void renderToFrameBuffer(void* fb)
{
	GX_DrawDone();
	
	GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
	GX_SetColorUpdate(GX_TRUE);
	GX_CopyDisp(fb,GX_TRUE);
}

